﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//nyArt
using System.Drawing.Imaging;
using jp.nyatla.nyartoolkit.cs;
using jp.nyatla.nyartoolkit.cs.core;
using jp.nyatla.nyartoolkit.cs.detector;
using NyARToolkitCSUtils.Capture;
using NyARToolkitCSUtils.NyAR;
using System.Runtime.InteropServices;
//Xna
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Puzzle_3D
{
    class Puzzle_Game : CaptureListener
    {
        //Anzahl der Bilder, die in der Liste gezeigt werden
        private int SHOWNUMBER = 6;
        private static int NEEDEDVALUES = 5;
        private static int GESTURETIME = 750;
        private static int CAPTUREWIDTH = 640;
        private static int CAPTUREHEIGHT = 480;
        private static float TRANSLATE_X_GROUND = 5800f;
        private static float TRANSLATE_Y_GROUND = 4000f;
        //private static float TRANSLATE_X_GROUND = 3500f;
        //private static float TRANSLATE_Y_GROUND = 2500f;

        private bool toolbarMarkerDetected;

        private Puzzle_PuzzleObject[] puzzleObjects;
        private Puzzle_PuzzleObject[] mainPuzzleObjects;
        private Puzzle_PuzzleObject[] playgroundObjects;
        private Puzzle_PuzzlePiece[] mainPuzzlePieces;
        private Texture2D playgroundTexture;
        private List<Puzzle_PuzzlePiece> imageList;
        private List<double> scrollValues;
        private List<double> putValues;
        private int puzzleNumber;
        private Vector2 putCoord;
        private bool leftScroll;
        private bool rightScroll;
        private bool putGesture;
        private int startCount;

        private GameTime time;
        private int foundTime;

        private CaptureDevice m_cap;
        private const String AR_CODE_FILE1 = "../../../nyArt/patt.hiro";
        private const String AR_CODE_FILE2 = "../../../nyArt/patt.kanji";
        private const String AR_CAMERA_FILE = "../../../nyArt/camera_para.dat";
        private NyARSingleDetectMarker m_ar;
        private NyARDetectMarker m_all;
        private DsBGRX32Raster m_raster;
        private int detected;

        private Puzzle root;
        private Texture2D cameraImage;
        private Matrix toolbarResultMatrix;
        private Matrix playgroundResultMatrix;
        private NyARDoublePoint3d myMarkerPoint;
        private NyARDoublePoint3d myMarkerPointPuzzle;

        private float aspectRatio;

        public Puzzle_Game(Puzzle _root, List<Puzzle_PuzzlePiece> _list, GameTime _time)
        {
            this.root = _root;
            this.imageList = _list;
            this.aspectRatio = (float)this.root.getViewport().Width / (float)this.root.getViewport().Height;
            this.cameraImage = new Texture2D(this.root.GraphicsDevice, CAPTUREWIDTH, CAPTUREHEIGHT, 1, TextureUsage.None, SurfaceFormat.Color);

            this.playgroundTexture = Texture2D.FromFile(this.root.GraphicsDevice, "..\\..\\..\\Content\\textures\\Feld.png");
            this.startCount = 0;
            this.leftScroll = this.rightScroll = this.putGesture = this.toolbarMarkerDetected = false;
            this.scrollValues = new List<double>(NEEDEDVALUES);
            this.putValues = new List<double>(NEEDEDVALUES);
            this.putCoord = new Vector2(0, 0);
            if (this.imageList != null)
            {
                this.puzzleObjects = new Puzzle_PuzzleObject[this.imageList.Count];

                this.playgroundObjects = new Puzzle_PuzzleObject[7];
                this.playgroundObjects[0] = new Puzzle_PuzzleObject(this.root.Content.Load<Model>("3D-Objekte\\ground"));

                //Positionen der abgelegten Puzzleteile festlegen
                float xPos = -1050f;
                float yPos = -600f;
                for (int i = 1; i < 7; i++)
                {
                    if (i == 4)
                    {
                        yPos = 500f;
                        xPos = -1050;
                    }
                    this.playgroundObjects[i] = new Puzzle_PuzzleObject(this.root.Content.Load<Model>("3D-Objekte\\puzzleItem"));
                    this.playgroundObjects[i].Position = new Vector2(xPos, yPos);

                    xPos += 1100f;
                }
                this.time = _time;
                this.foundTime = 0;
                this.mainPuzzlePieces = new Puzzle_PuzzlePiece[6]; //praesentiert das Spielfeld
                this.mainPuzzleObjects = new Puzzle_PuzzleObject[6];
                this.puzzleObjects = new Puzzle_PuzzleObject[this.imageList.Count];
                for (int i = 0; i < this.imageList.Count; i++)
                {
                    this.puzzleObjects[i] = new Puzzle_PuzzleObject(this.root.Content.Load<Model>("3D-Objekte\\puzzleItem"));
                }
            }

            //Markererkennung
            NyARParam ap = new NyARParam();
            ap.loadARParamFromFile(AR_CAMERA_FILE);
            ap.changeScreenSize(CAPTUREWIDTH, CAPTUREHEIGHT);

            NyARCode[] codes = new NyARCode[2];
            codes[0] = new NyARCode(16, 16);
            codes[1] = new NyARCode(16, 16);
            codes[0].loadARPattFromFile(AR_CODE_FILE1);
            codes[1].loadARPattFromFile(AR_CODE_FILE2);

            NyARTransMatResult result_mat = new NyARTransMatResult();

            //Kamera initialisieren
            CaptureDeviceList cl = new CaptureDeviceList();
            CaptureDevice cap = cl[0];
            cap.SetCaptureListener(this);
            cap.PrepareCapture(CAPTUREWIDTH, CAPTUREHEIGHT, 30);
            this.m_cap = cap;

            this.m_raster = new DsBGRX32Raster(cap.video_width, cap.video_height, cap.video_width * 32 / 8);

            double[] markerwidths = {80f,80f};

            this.m_all = new NyARDetectMarker(ap, codes, markerwidths, 2, m_raster.getBufferType());
            this.m_all.setContinueMode(false);

            this.m_ar = new NyARSingleDetectMarker(ap, codes[0], 80.0, this.m_raster.getBufferType());
            this.m_ar.setContinueMode(false);
        }

        private Matrix convertMatrix(NyARTransMatResult result_mat)
        {
            Matrix resultx = new Matrix();

            resultx.M11 = (float)result_mat.m00;
            resultx.M12 = (float)result_mat.m01;
            resultx.M13 = (float)result_mat.m02;
            resultx.M21 = (float)result_mat.m10;
            resultx.M22 = (float)result_mat.m11;
            resultx.M23 = (float)result_mat.m12;
            resultx.M31 = (float)result_mat.m20;
            resultx.M32 = (float)result_mat.m21;
            resultx.M33 = (float)result_mat.m22;

            resultx.M14 = -(float)result_mat.m03 * 25;
            resultx.M24 = -(float)result_mat.m13 * 25;
            resultx.M34 = -(float)result_mat.m23 * 10;

            resultx.M44 = 1f;

            return resultx;

        }

        public void OnBuffer(CaptureDevice i_sender, double i_sample_time, IntPtr i_buffer, int i_buffer_len)
        {
            byte[] bgrData = new byte[i_buffer_len];
            Marshal.Copy(i_buffer, bgrData, 0, i_buffer_len);
            Color[] colorData = new Color[i_buffer_len / 4];
            int index = 0;

            
            for (int i = 0; i < colorData.Length; i++)
            {
                index = i * 4;
                colorData[colorData.Length - i - 1] = new Color(bgrData[index + 2], bgrData[index + 1], bgrData[index]);
            }

            this.root.GraphicsDevice.Textures[0] = null;

            this.cameraImage.SetData<Microsoft.Xna.Framework.Graphics.Color>(colorData);

            //Ausgabe
            this.m_raster.setBuffer(i_buffer, true);

            detected = 0;
            int detected_markers = this.m_all.detectMarkerLite(this.m_raster, 100);
            for (int i=0; i < detected_markers; i++)
            {
                if (this.m_all.getConfidence(i) > .4)
                {
                    detected++;
                }
            }
            if (detected > 0)
            {
                this.toolbarMarkerDetected = false;
                this.playgroundObjects[0].Detected = false;
                for (int i = 0; i < detected; i++)
                {
                    int codeIndex = m_all.getARCodeIndex(i);
                    //ground
                    if (codeIndex == 0)
                    {
                        NyARDoublePoint3d temp = new NyARDoublePoint3d();
                        NyARDoublePoint3d tempOut = new NyARDoublePoint3d();

                        NyARTransMatResult result_mat = new NyARTransMatResult();
                        this.m_all.getTransmationMatrix(i, result_mat);
                        result_mat.transformVertex(temp, tempOut);

                        this.playgroundResultMatrix = this.convertMatrix(result_mat);

                        this.playgroundObjects[0].Detected = true;

                        this.myMarkerPointPuzzle = tempOut;
                    }
                    //toolbar
                    if (codeIndex == 1)
                    {
                        NyARTransMatResult result_mat = new NyARTransMatResult();

                        this.m_all.getTransmationMatrix(i, result_mat);

                        NyARDoublePoint3d temp = new NyARDoublePoint3d();
                        NyARDoublePoint3d tempOut = new NyARDoublePoint3d();
                        result_mat.transformVertex(temp, tempOut);

                        this.toolbarResultMatrix = this.convertMatrix(result_mat);

                        this.toolbarMarkerDetected = true;

                        this.myMarkerPoint = tempOut;

                        NyARDoublePoint3d tempAngle = new NyARDoublePoint3d();
                        result_mat.getZXYAngle(tempAngle);

                        double angleX = (tempAngle.x * 180) / Math.PI;
                        double angleY = (tempAngle.y * 180) / Math.PI;

                        Console.WriteLine(angleX.ToString());

                        this.scrollValues.Add(angleY);
                        this.putValues.Add(angleX);
                        this.putCoord.X += (float)myMarkerPoint.x;
                        this.putCoord.Y += (float)myMarkerPoint.y;

                        if (this.scrollValues.Count >= NEEDEDVALUES)
                        {
                            this.checkForScrollGesture();
                            this.scrollValues.Clear();
                        }
                        if (this.putValues.Count >= NEEDEDVALUES)
                        {
                            this.checkForPutPuzzleGesture();
                            this.putValues.Clear();
                            this.putCoord = Vector2.Zero;
                        }
                    }
                }
                if (!this.root.EndOfGame)
                {
                    this.checkForEndOfGame();
                }
            }
        }

        private void checkForScrollGesture()
        {
            if (((int)this.time.TotalGameTime.TotalMilliseconds - (int)this.foundTime) >= GESTURETIME)
            {
                int leftCount = 0;
                int rightCount = 0;
                foreach (double angle in this.scrollValues)
                {
                    if (angle >= -160.0 && angle <= -140.0)
                    {
                        leftCount++;
                    }
                    if (angle >= 140.0 && angle <= 160.0)
                    {
                        rightCount++;
                    }
                }
                if (leftCount == NEEDEDVALUES)
                {
                    this.leftScroll = true;
                    this.foundTime = (int)this.time.TotalGameTime.TotalMilliseconds;
                }
                if (rightCount == NEEDEDVALUES)
                {
                    this.rightScroll = true;
                    this.foundTime = (int)this.time.TotalGameTime.TotalMilliseconds;
                }
            }

            if (this.leftScroll)
            {
                if (startCount >= this.puzzleObjects.Count())
                {
                    this.startCount = 0;
                }
                else
                {
                    this.startCount++;
                }
                this.leftScroll = false;
            }
            else if (this.rightScroll)
            {
                if (startCount == 0)
                {
                    this.startCount = this.puzzleObjects.Count() - 1;
                }
                else
                {
                    this.startCount--;
                }
                this.rightScroll = false;
            }
            if (this.puzzleObjects.Count() == 1)
            {
                this.startCount = 0;
            }
        }

        private void checkForPutPuzzleGesture()
        {
            if (((int)this.time.TotalGameTime.TotalMilliseconds - (int)this.foundTime) >= GESTURETIME)
            {
                int count = 0;
                foreach (double angle in this.putValues)
                {
                    if (angle >= 20.0 && angle <= 25.0)
                    {
                        count++;
                    }
                }
                if (count == NEEDEDVALUES)
                {
                    this.putGesture = true;
                    this.foundTime = (int)this.time.TotalGameTime.TotalMilliseconds;
                }
            }

            if (this.putGesture)
            {
                //Durchschnittskoordinaten
                this.putCoord.X = this.putCoord.X / NEEDEDVALUES;
                this.putCoord.Y = this.putCoord.Y / NEEDEDVALUES;
                //Bereich festlegen
                bool putIn = false;
                if(this.putCoord.X <= 105.0f && this.putCoord.X >= 35.0f)
                {
                    if (this.putCoord.Y < 0)
                    {
                        this.puzzleNumber = 0;
                        putIn = true;
                    }
                    else
                    {
                        this.puzzleNumber = 3;
                        putIn = true;
                    }
                }
                else if (this.putCoord.X <= 35.0f && this.putCoord.X >= -35.0f)
                {
                    if (this.putCoord.Y < 0)
                    {
                        this.puzzleNumber = 1;
                        putIn = true;
                    }
                    else
                    {
                        this.puzzleNumber = 4;
                        putIn = true;
                    }
                }
                else if (this.putCoord.X <= -35.0f && this.putCoord.X >= -105.0f)
                {
                    if (this.putCoord.Y < 0)
                    {
                        this.puzzleNumber = 2;
                        putIn = true;
                    }
                    else
                    {
                        this.puzzleNumber = 5;
                        putIn = true;
                    }
                }

                if (putIn)
                {
                    bool inserted = false;
                    bool replaced = false;
                    bool replaceLast = false;
                    if (this.mainPuzzlePieces[this.puzzleNumber] == null)
                    {
                        //Objekte und Texturen sichern
                        this.mainPuzzlePieces[this.puzzleNumber] = this.imageList.ElementAt(this.startCount);
                        this.mainPuzzleObjects[this.puzzleNumber] = this.puzzleObjects.ElementAt(this.startCount);

                        inserted = true;
                    }
                    else
                    {
                        //vorhandene Objekte wieder herstellen
                        this.imageList.Add(this.mainPuzzlePieces[this.puzzleNumber]);
                        Array.Resize<Puzzle_PuzzleObject>(ref this.puzzleObjects, this.puzzleObjects.Length + 1);
                        this.puzzleObjects[this.puzzleObjects.Length - 1] = this.mainPuzzleObjects[this.puzzleNumber];

                        if (this.imageList.Count == 1)
                        {
                            //Puzzle loeschen
                            this.mainPuzzlePieces[this.puzzleNumber] = null;
                            this.mainPuzzleObjects[this.puzzleNumber] = null;
                            replaceLast = true;
                        }
                        else
                        {
                            //Puzzle ueberschreiben
                            this.mainPuzzlePieces[this.puzzleNumber] = this.imageList.ElementAt(this.startCount);
                            this.mainPuzzleObjects[this.puzzleNumber] = this.puzzleObjects.ElementAt(this.startCount);
                            replaced = true;
                        }
                    }

                    if ((inserted || replaced) && !replaceLast)
                    {
                        //Objekte und Texturen loeschen
                        this.imageList.RemoveAt(this.startCount);
                        List<Puzzle_PuzzleObject> temp = this.puzzleObjects.ToList<Puzzle_PuzzleObject>();
                        temp.RemoveAt(this.startCount);
                        this.puzzleObjects = temp.ToArray<Puzzle_PuzzleObject>();
                    }

                    if (this.imageList.Count < SHOWNUMBER)
                    {
                        --SHOWNUMBER;
                    }
                    else if (SHOWNUMBER < 6 && !replaced)
                    {
                        ++SHOWNUMBER;
                    }
                }
                this.putGesture = false;
            }
        }

        private void checkForEndOfGame()
        {
            Puzzle_PuzzlePiece temp = this.mainPuzzlePieces[0];
            int tempCount = 0;
            for (int i = 0; i < this.mainPuzzlePieces.Count(); i++)
            {
                if (this.mainPuzzlePieces[i] != null &&
                    this.mainPuzzlePieces[i].getPosition() == i &&
                    this.mainPuzzlePieces[i].getName().Equals(temp.getName()))
                {
                    tempCount++;
                }
            }
            if (tempCount == 6)
            {
                this.root.EndOfGame = true;
                this.root.EndTime = (int)this.time.TotalGameTime.TotalMilliseconds;
            }
        }

        public void startCapturing()
        {
            this.m_cap.StartCapture();
        }

        public void endCapturing()
        {
            this.m_cap.StopCapture();
        }

        // Set the position of the model in world space, and set the rotation.
        Vector3 modelPosition = Vector3.Zero;
        //float modelRotation = 0.0f;

        // Set the position of the camera in world space, for our view matrix.
        Vector3 cameraPosition = new Vector3(0.0f, 50.0f, 5000.0f);

        private void drawPlayground()
        {
            foreach (Puzzle_PuzzleObject pobject in this.playgroundObjects)
            {
                pobject.TransformationMatrix = new Matrix[pobject.getModel().Bones.Count];
                pobject.getModel().CopyAbsoluteBoneTransformsTo(pobject.TransformationMatrix);
            }

            Vector3 pos = new Vector3((float)this.myMarkerPointPuzzle.x * -10f, (float)this.myMarkerPointPuzzle.y * -10f, (float)this.myMarkerPointPuzzle.z * -10f);

            for (int i = 0; i < this.playgroundObjects.Count(); i++)
            {
                foreach (ModelMesh mesh in this.playgroundObjects[i].getModel().Meshes)
                {
                    // This is where the mesh orientation is set, as well as our camera and projection.
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        //Licht
                        effect.EnableDefaultLighting();
                        effect.PreferPerPixelLighting = true;
                        effect.AmbientLightColor = new Vector3(0.5f, 0.5f, 0.5f);
                        effect.DirectionalLight0.Direction = new Vector3(0, -1, -1);

                        if (i == 0)
                        {
                            effect.Texture = this.playgroundTexture;
                            effect.World = this.playgroundObjects.ElementAt(i).TransformationMatrix[mesh.ParentBone.Index]
                                * Matrix.CreateScale(95f)
                                * Matrix.CreateRotationX((float)(90.0 * Math.PI / 180.0))
                                //* Matrix.CreateRotationZ(90.0f)
                                * Matrix.CreateTranslation(TRANSLATE_X_GROUND, TRANSLATE_Y_GROUND, 0f)
                                * Matrix.Transpose(this.playgroundResultMatrix)
                                //* Matrix.CreateRotationZ((float)(r * Math.PI / 180))
                                ;
                        }
                        else if (this.mainPuzzlePieces[i - 1] != null)
                        {
                            effect.Texture = this.mainPuzzlePieces[i - 1].getPieceTexture();
                            effect.World = this.playgroundObjects.ElementAt(i).TransformationMatrix[mesh.ParentBone.Index]
                                * Matrix.CreateScale(60f)
                                * Matrix.CreateRotationX((float)(90.0 * Math.PI / 180.0))
                                * Matrix.CreateTranslation(this.playgroundObjects[i].Position.X + TRANSLATE_X_GROUND, this.playgroundObjects[i].Position.Y + TRANSLATE_Y_GROUND, -100f)
                                * Matrix.Transpose(this.playgroundResultMatrix);
                        }
                        effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
                        effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                            this.aspectRatio, 1.0f, 100000.0f);
                    }
                    // Draw the mesh, using the effects set above.
                    mesh.Draw();
                }
            }
        }
        
        private void drawToolbar()
        {
            float objectPos = 0f;
            int index = 0;
            bool resetIndex = false;
            if (this.imageList != null && this.imageList.Count != 0)
            {
                foreach (Puzzle_PuzzleObject pobject in this.puzzleObjects)
                {
                    pobject.TransformationMatrix = new Matrix[pobject.getModel().Bones.Count];
                    pobject.getModel().CopyAbsoluteBoneTransformsTo(pobject.TransformationMatrix);
                }

                for (int i = 0; i < SHOWNUMBER; i++)
                {
                    if (this.startCount + SHOWNUMBER > this.puzzleObjects.Count() - 1 && !resetIndex)
                    {
                        if ((this.startCount + i) > this.puzzleObjects.Count() - 1)
                        {
                            resetIndex = true;
                            index = 0;
                        }
                        else
                        {
                            index = this.startCount + i;
                        }
                    }
                    else if (!resetIndex)
                    {
                        index = startCount + i;
                    }
                    foreach (ModelMesh mesh in this.puzzleObjects.ElementAt(index).getModel().Meshes)
                    {
                        foreach (BasicEffect effect in mesh.Effects)
                        {
                            effect.Texture = this.imageList.ElementAt(index).getPieceTexture();
                            //Licht
                            effect.EnableDefaultLighting();
                            effect.PreferPerPixelLighting = true;
                            effect.AmbientLightColor = new Vector3(0.5f, 0.5f, 0.5f);
                            effect.DirectionalLight0.Direction = new Vector3(0, -1, -1);

                            effect.World = this.puzzleObjects.ElementAt(index).TransformationMatrix[mesh.ParentBone.Index]
                                * Matrix.CreateScale(40f)
                                * Matrix.CreateRotationX((float)(90.0 * Math.PI / 180.0))
                                * Matrix.CreateTranslation(objectPos, 0f, 0f)
                                * Matrix.Transpose(this.toolbarResultMatrix);
                            effect.View = Matrix.CreateLookAt(this.cameraPosition, Vector3.Zero, Vector3.Up);
                            effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                                this.aspectRatio, 1.0f, 100000.0f);
                        }
                        mesh.Draw();
                    }
                    objectPos += 900f;
                    if (resetIndex)
                    {
                        index++;
                    }
                }
            }
        }

        private void drawCameraImage()
        {
            this.root.getSpriteBatch().Begin(SpriteBlendMode.None);
            this.root.getSpriteBatch().Draw(this.cameraImage, new Rectangle(0, 0, 800, 600), Color.White);
            this.root.getSpriteBatch().End();
        }

        public void Draw()
        {
            float modelRotation = root.modelRotation;

            this.drawCameraImage();

            if (this.playgroundObjects[0].Detected)
            {
                this.drawPlayground();
            }
            if (this.toolbarMarkerDetected)
            {
                this.drawToolbar();
            }
        }
    }
}